/*
 * worker.c
 *
 * Copyright (C) 2011-2012 Dan Reidy <dubkat@ratbox.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 *
 *       Foundation, Inc.
 *       59 Temple Place - Suite 330
 *       Boston, MA  02111-1307, USA.
 *
 *
 *  Project Name: ratbox-monitor
 *    Created on: Dec 1, 2010
 *        Author: Daniel J Reidy <dubkat@ratbox.org>
 *            Id: $Id: worker.c 52 2015-02-15 03:01:58Z dubkat $
 */

#include "ratbox_lib.h"
#include "libconfig.h"
#include "setup.h"
#include "uthash.h"
#include "settings.h"
#include "worker.h"
#include "numerics.h"
#include "common.h"
#include "dnsbl.h"
#include "logger.h"
#include "pcre.h"
#include "openmp.h"

extern config_t conf;

// this holds our hashed structure of clients
struct my_clients *clients = NULL;
struct my_opers *opers = NULL;

void handle(struct conn *, int, char **);
static void write_sendq(rb_fde_t *, void *);
static void nick_from_nuh(const char *, char *);
static struct my_clients *hash_find_client(const char *);
static void handle_PING(struct conn *, int, char **);
static void handle_NOTICE(struct conn *, int, char **);
static void handle_PRIVMSG(struct conn *, int, char **);
static void hash_empty_clients(void);
unsigned int hash_find_client_rx(struct conn*, const char *, const char *, int );
static void handle_server_error(void);
void connect_to_server(void *);
static void handle_wallop(struct conn *, int, char **);
void check_dnsbl(struct conn *, struct my_clients *);
int get_bantype(const char *);
void send_queued(struct conn *);
int file_exists(const char *);

static const char *banReasonTable[] = {
	"Random drone-like nicks are prohibited.", "Flooding is prohibited.",
	"Attempting to join juped channels.", "Hammering."
};

static const char *confBanType[] = { "seedrand.ban_type", };

static const char *confBanTime[] = { "seedrand.ban_time", };

static const char *banType[] = { "none", "kill", "kline", "dline", "xline",

	NULL,
};

static const char *banTable[] = { "NONE", "KILL", "KLINE", "DLINE", "XLINE",

	NULL,
};

void
write_sendq(rb_fde_t *F, void *data)
{
	struct conn *serv = data;
	send_queued(serv);
}

void
send_queued(struct conn *serv)
{
	int retlen;
	while((retlen = rb_linebuf_flush(serv->F, &serv->sendq)) > 0)
		;;

	if(retlen == 0 || (retlen < 0 && !rb_ignore_errno(errno)))
	{
		/* XXX handle this error condition */
		return;
	}
	if(rb_linebuf_len(&serv->sendq))
		rb_setselect(serv->F, RB_SELECT_WRITE, write_sendq, serv);
}

void
sendto_one(struct conn *serv, const char *format, ...)
{
	va_list args;
	buf_head_t linebuf;

	rb_linebuf_newbuf(&linebuf);
	va_start(args, format);
	rb_linebuf_putmsg(&linebuf, format, &args, NULL);
	va_end(args);
	rb_linebuf_attach(&serv->sendq, &linebuf);
	rb_linebuf_donebuf(&linebuf);
	send_queued(serv);
}

int
get_bantype(const char *type)
{
	int i;
	for(i = 0; banType[i] != NULL; i++)
	{
		if(!strcasecmp(banType[i], type))
			return i;
	}
	return INVALID;
}

/**
 * Ban the client from the server
 * @input connection struct, client struct, ban type
 * @return none
 */
int
ban_client(struct conn *serv, struct my_clients *cli, int type)
{
	const char *conf_bantype;
	int conf_bantime;
	int bantype;
	char putserv[IRCD_BUFSIZE];

	// they are already banned or spoofed, nothing to for us to do.
	if(cli->isBanned || cli->isSpoofed)
		return NO;

	config_lookup_string(&conf, confBanType[type], &conf_bantype);

	bantype = get_bantype(conf_bantype);
	if(bantype == INVALID)
		return NO;

	cli->isBanned = YES;

	if(bantype != KILL)
		config_lookup_int(&conf, confBanTime[type], &conf_bantime);

	switch (bantype)
	{
	case KILL:
		rb_snprintf(putserv, IRCD_BUFSIZE, "KILL %s %s", cli->nick, banReasonTable[type]);
		break;

	case XLINE:
		logger(INFO, "WARNING: XLINE is not supported yet... K-Lining instead.");
		// fall-though to kline

	case KLINE:
		rb_snprintf(putserv, IRCD_BUFSIZE, "KLINE %i %s@%s :%s", conf_bantime,
			    (*(cli->user) == '~') ? "*" : cli->user, cli->ip, banReasonTable[type]);
		break;

	case DLINE:
		rb_snprintf(putserv, IRCD_BUFSIZE, "DLINE %i %s :%s", conf_bantime,
			    cli->ip, banReasonTable[type]);
		break;

	default:		// shouldn't get here.
		break;

	}

	//report_to_channel(serv, "DEBUG -> Issuing command: %s", putserv);
	sendto_one(serv, putserv);	// commented out while testing
	return YES;
}

void
report_to_channel(struct conn *serv, const char *format, ...)
{
	va_list args;
	config_setting_t *chanlist;
	config_setting_t *each;
	char *string;
	int i;

	int size = (IRCD_BUFSIZE * sizeof(char));

	if((string = malloc(size)) == NULL)
	{
		logger(CRIT, "ERROR: OUT OF MEMORY IN report_to_channel!");
		return;
	}
	va_start(args, format);
	vsnprintf(string, size, format, args);
	va_end(args);

	chanlist = config_lookup(&conf, "channels");
	for(i = 0; i < config_setting_length(chanlist); i++)
	{
		each = config_setting_get_elem(chanlist, i);
		if(config_setting_get_bool(config_setting_get_member(each, "vocal")))
		{
			sendto_one(serv,
				   "PRIVMSG %s :%s",
				   config_setting_get_string(config_setting_get_member
							     (each, "name")), string);
		}
	}
	rb_free(string);
}

void
join_channels(struct conn *serv)
{
	config_setting_t *chanlist;
	config_setting_t *each;
	unsigned int i;

	// channels are a "list"
	chanlist = config_lookup(&conf, "channels");
	for(i = 0; i < config_setting_length(chanlist); i++)
	{

		each = config_setting_get_elem(chanlist, i);

		sendto_one(serv,
			   "JOIN %s %s",
			   config_setting_get_string(config_setting_get_member(each, "name")),
			   config_setting_get_string(config_setting_get_member(each, "key")));
	}
}

void
hash_add_oper(struct my_opers *o)
{
	HASH_ADD_INT(opers, id, o);
}

void
hash_add_client(struct my_clients *s)
{
	HASH_ADD_INT(clients, id, s);
}

/* delete a user from the client hash by it's id
 * you therefor need to use hash_find_client to first find it's id.
 * @input struct my_clients *client (the structure of the client to be removed)
 * @output none.
 */
void
hash_del_client(struct my_clients *client)
{
	HASH_DEL(clients, client);
	rb_free(client);
}

/* find a user in the client hash by nickname
 * @input const char * nick
 * @output struct my_clients *
 */
struct my_clients *
hash_find_client(const char *nick)
{
	struct my_clients *current_user, *tmp;
	struct my_clients *r = NULL;

#	pragma omp parallel
	{
		logger(INFO, "Threads: (hash_find_client) I am thread %d out of %d.",
		       omp_get_thread_num(), omp_get_num_threads());
		HASH_ITER(hh, clients, current_user, tmp)
		{
			if(!irccmp(current_user->nick, nick))
				r = current_user;
		}
	}

	return r;
}

/**
 * pattern: the textual regex we will use to find a client
 * *table[]: a char array table to store what we have found
 */
unsigned int
hash_find_client_rx(struct conn *serv, const char *dest, const char *pattern, int mode)
{

	pcre *re;
	const char *error;
	char *sub;
	int erroffset;
	int ovector[PCRE_MAX_MATCH_COUNT];
	int subject_len;
	int rc;
	unsigned int i = 0;

	struct my_clients *user, *tmp;
	struct my_clients *r = NULL;


	re = pcre_compile(pattern, 0, &error, &erroffset, NULL);
	if ( re == NULL ) {
		/* XXX this should really report back to the source that their regex is bunk */
		logger(INFO, "PCRE compilation failed at offset %d: %s", erroffset, error);
		return 0;
	}

	// walk the hash, and attempt to match //
	HASH_ITER(hh, clients, user, tmp)
	{
		char usermask[MAX_USERHOST_LEN] = {0};
		// rb_snprintf(string, IRCD_BUFSIZE, "%s", parv[3]);
		rb_snprintf(usermask, sizeof(usermask), "%s!%s@%s", user->nick, user->user, user->host);

		rc = pcre_exec(re, NULL, usermask, (int)strlen(usermask), 0, 0, ovector, PCRE_MAX_MATCH_COUNT);

		if ( rc < 0 ) {
			switch(rc)
			{
			case PCRE_ERROR_NOMATCH:
				break;

			default:
				logger(DEBUG, "hash_find_client_rx: Match Error: %d", rc);
				break;
			}
		}

		if ( rc == 0 )
		{
			rc = PCRE_MAX_MATCH_COUNT/3;
			logger(DEBUG, "hash_find_client_rx: ovector ran out of room, too many substrings: %d", rc - 1);
		}

		if ( rc > 0 )
		{
			logger(DEBUG, "hash_find_client_rx: Match found at offset %d", ovector[0] );
			if ( i < PCRE_MAX_MATCH_COUNT ) {
				sendto_one(serv, "PRIVMSG %s :FIND -> %s", dest, usermask);
			}
			i++;
		}
	}

	pcre_free(re);
	return i;
}


unsigned int
hash_count_clients_by_host(const char *host)
{
	unsigned int count = 0;
	struct my_clients *current_user, *tmp;

#	pragma omp parallel reduction(+:count)
	{
		logger(INFO,
		       "Threads: (hash_count_clients_by_host) I am thread %d out of %d.",
		       omp_get_thread_num(), omp_get_num_threads());
		HASH_ITER(hh, clients, current_user, tmp)
		{
			if(!irccmp(current_user->host, host))
				count++;
		}
	}
	return count;
}

unsigned int
hash_count_clients_by_ip(const char *ip)
{
	unsigned int count = 0;
	struct my_clients *current_user, *tmp;

#	pragma omp parallel reduction(+:count)
	{
		logger(INFO,
		       "Threads: (hash_count_clients_by_ip) I am thread %d out of %d.",
		       omp_get_thread_num(), omp_get_num_threads());
		HASH_ITER(hh, clients, current_user, tmp)
		{
			if(!irccmp(current_user->ip, ip))
				count++;
		}
	}
	return count;
}

unsigned int
hash_count_clients_by_userhost(const char *user, const char *host)
{
	unsigned int count = 0;
	struct my_clients *current_user, *tmp;

#	pragma omp parallel reduction(+:count)
	{
		logger(INFO,
		       "Threads: (hash_count_clients_by_userhost) I am thread %d out of %d.",
		       omp_get_thread_num(), omp_get_num_threads());
		HASH_ITER(hh, clients, current_user, tmp)
		{
			if(!irccmp(current_user->host, host) && !irccmp(current_user->user, user))
				count++;
		}
	}
	return count;
}


static void
hash_empty_clients(void)
{
	struct my_clients *current_user, *tmp;

#	pragma omp parallel
	{
		logger(INFO, "Threads: (hash_empty_clients) I am thread %d out of %d.",
		       omp_get_thread_num(), omp_get_num_threads());
		HASH_ITER(hh, clients, current_user, tmp)
		{
			HASH_DEL(clients, current_user);
			/* delete it (users advances to next) */
			rb_free(current_user);	/* free it */
		}
	}
}

int
time_diff(struct timeval *result, struct timeval *x, struct timeval *y)
{
	/* Perform the carry for the later subtraction by updating y. */
	if(x->tv_usec < y->tv_usec)
	{
		int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
		y->tv_usec -= 1000000 * nsec;
		y->tv_sec += nsec;
	}
	if(x->tv_usec - y->tv_usec > 1000000)
	{
		int nsec = (x->tv_usec - y->tv_usec) / 1000000;
		y->tv_usec += 1000000 * nsec;
		y->tv_sec -= nsec;
	}

	/* Compute the time remaining to wait.
	   tv_usec is certainly positive. */
	result->tv_sec = x->tv_sec - y->tv_sec;
	result->tv_usec = x->tv_usec - y->tv_usec;

	/* Return 1 if result is negative. */
	return x->tv_sec < y->tv_sec;
}

char *
elapse(int begin)
{
	static struct timeval start;
	struct timeval stop, delay;
	static char string[IRCD_BUFSIZE];

	if(begin)
	{
		if((gettimeofday(&start, NULL)) == -1)
		{
			perror("gettimeofday");
			exit(EXIT_FAILURE);
		}
		return 0;
	}

	if((gettimeofday(&stop, NULL)) == -1)
	{
		perror("gettimeofday");
		exit(EXIT_FAILURE);
	}

	time_diff(&delay, &stop, &start);
	sprintf(string, "%li.%4li", delay.tv_sec, delay.tv_usec);
	return string;

}

/* generate an auto-incrementing id for new clients */
unsigned int
new_id(int reset)
{
	static unsigned int id = 0;
	if(reset)
		return id;

	return ++id;
}

/* figure out a nickname from a nick!user@host string
 * @input const char *source, char *destination
 * @output none.
 */
void
nick_from_nuh(const char *source, char *dest)
{
	char *p = dest;

	if(strchr(source, '!') == NULL)	// not a proper nick!user@host
		return;

	while(*source != '!')
		*p++ = *source++;

	*p++ = '\0';
}

/* figure out a user@host from a nick!user@host string
 * @input const char *source, char *destination
 * @output none.
 */
void
userhost_from_nuh(const char *source, char *dest)
{
	char *p = dest;

	if(strchr(source, '!') == NULL)	// not a proper nick!user@host
		return;

	while(*source != '!')
		*source++;

	*source++;

	while(*source)
		*p++ = *source++;

	*p++ = '\0';
}

// a generic notice handler. this passes off info to more specific functions
void
handle(struct conn *serv, int parc, char **parv)
{
	int num = -1;

	// This whole function could probably be done more elegantly

	if(!strcmp(parv[0], "PING"))
		handle_PING(serv, parc, parv);

	else if(!strcmp(parv[0], "ERROR") || !strcmp(parv[0], "RROR"))
	{
		logger(INFO, "%s: %s", parv[0], parv[1]);
		handle_server_error();
	}

	else if(!strcmp(parv[1], "WALLOPS"))
		handle_wallop(serv, parc, parv);

	else if(!strcmp(parv[1], "NOTICE"))
		handle_NOTICE(serv, parc, parv);

	else if(!strcmp(parv[1], "PRIVMSG"))
		handle_PRIVMSG(serv, parc, parv);

	else if((strlen(parv[1]) == 3) && isdigit(*(parv[1]))
		&& isdigit(*(parv[1] + 1)) && isdigit(*(parv[1] + 2)))
	{
		num = atoi(parv[1]);
		switch (num)
		{
		case 001:
			handle_001(serv, parc, parv);
			break;

		case 004:
			handle_004(serv, parc, parv);
			break;

		case 005:	// ISUPPORT
			handle_005(serv, parc, parv);
			break;

		case 262:
			handle_262(serv, parc, parv);
			break;

		case 243:
			handle_243(serv, parc, parv);
			break;

		case 433:
			handle_433(serv, parc, parv);
			break;

		case 376:
			handle_376(serv, parc, parv);
			break;

		case 381:
			handle_381(serv, parc, parv);
			break;

		case 491:
			handle_491(serv, parc, parv);
			break;

		case 708:
			handle_TRACE(serv, parc, parv);
			break;

		default:
			break;
		}
	}

}

/* clean up the client hash and free(), reset the user id counter to 0, and reconnect */
static void
handle_server_error(void)
{
	logger(INFO, "Server Error. Reconnecting...");
	hash_empty_clients();
	new_id(YES);
	rb_event_addonce("connect_to_server", connect_to_server, NULL, 1);
}

int
check_seedscore(const struct my_clients *cli)
{
	static int sr_b_score = -1;
	if(sr_b_score == -1)
	{
		config_lookup_int(&conf, "seedrand.ban_score", &sr_b_score);
		if(sr_b_score < SEED_BAN_MIN)
			sr_b_score = SEED_BAN_MIN;	// lock the lowest min seed for a ban
		// to a reasonable level
	}

	if(cli->seed >= sr_b_score)
		return YES;
	return NO;
}

static void
handle_wallop(struct conn *serv, int parc, char **parv)
{
	/*
	   struct MemoryStruct *mem;
	   struct my_clients *cli, *tmp;

	   const char *kline_url;

	   char opernick[12];
	   char operhost[255];

	   char *ptr = NULL;
	   char *pts = NULL;
	   int i;
	   //int len;
	   unsigned int matched = 0;
	   char user[12];
	   char host[255];
	   char reason[255];
	   char *token;
	   int count = 0;
	   int auto_apply = 0, auto_apply_matches = 0, kline_time = 0;
	   char runtime[10];
	 */

	/* efnet specific code ripped away */

}

static void
handle_NOTICE(struct conn *serv, int parc, char **parv)
{
#	pragma omp
	{
		logger(INFO, "Threads: (handle_notice) I am thread %d out of %d.",
		       omp_get_thread_num(), omp_get_num_threads());

		struct my_clients *cli;
		struct server *s = &my_server;
		struct status *me = &my_status;

		const char killitwithfire[] = "*** Notice -- ";
		int i, c, ntype;
		char *array[MAXPARA];
		char string[IRCD_BUFSIZE + 1];
		char *ptr = &string;
		//size_t sz = strlen(parv[3]) + 1;

		// not a notice from our server, then we dont want it.
		if(!strcmp(parv[0], s->servername))
			return;

		cli = rb_malloc(sizeof(struct my_clients));
		if(cli == NULL)
		{
			logger(CRIT, "ERROR: OUT OF MEMORY IN handle_NOTICE");
			exit(EXIT_FAILURE);
		}

		if(strcasecmp(parv[3], killitwithfire) > 0
		   && (strlen(parv[3]) - strlen(killitwithfire)) > 0)
		{
			for(i = 0; i < strlen(killitwithfire); i++)
				*(parv[3])++;	// get rid of the useless *** Notice --\s
		}

		/* string now holds the message, ptr is now a pointer to it */
		rb_snprintf(string, IRCD_BUFSIZE, "%s", parv[3]);
		c = rb_string_to_array(ptr, array, MAXPARA);

		if (me->foreground)
			for(i = 0; i < c; i++)
				fprintf(stdout, "array[%d]: %s\n", i, array[i]);

		// first the ratbox-3 Extended CONNECT/EXITs
		// CLICONN dubkat2 ~dubkat2 cpe-my-ip.blah.res.rr.com xx.xx.xx.xx restricted * * 0 testing
		// CLIEXIT dubkat2 ~dubkat2 cpe-my-ip.blah.res.rr.com xx.xx.xx.xx 0 Ping timeout: 192 seconds
		if(s->hasExtendedInfo)
		{
			if(!strcmp(array[0], "CLICONN"))
			{
				/* blow this up into yet another array, so that the gecos is untouched. */
				char *new_array[MAXPARA];
				char new_string[IRCD_BUFSIZE + 1];
				char *newptr = &new_string;
				rb_snprintf(new_string, IRCD_BUFSIZE, "%s", parv[3]);
				rb_string_to_array(newptr, new_array, 10);

				rb_strlcpy(cli->nick, new_array[1], sizeof(cli->nick));
				rb_strlcpy(cli->user, new_array[2], sizeof(cli->user));
				rb_strlcpy(cli->host, new_array[3], sizeof(cli->host));
				rb_strlcpy(cli->ip, new_array[4], sizeof(cli->ip));
				rb_strlcpy(cli->class, new_array[5], sizeof(cli->class));
				rb_strlcpy(cli->extra1, new_array[6], sizeof(cli->extra1));
				rb_strlcpy(cli->extra2, new_array[7], sizeof(cli->extra2));
				rb_strlcpy(cli->gecos, new_array[8], sizeof(cli->gecos));

				cli->id = new_id(NO);
				cli->seed = seedscore(cli->nick);
				cli->isSpoofed = NO;
				cli->isBanned = NO;
				if(!strcmp(cli->ip, "255.255.255.255"))
					cli->isSpoofed = YES;

				hash_add_client(cli);

				logger(DEBUG,
				       "NEW USER: id[%i]: nick(%s) user(%s) host(%s) ip(%s) class(%s) spoofed(%s) seed(%i)",
				       cli->id, cli->nick, cli->user, cli->host, cli->ip,
				       cli->class, cli->isSpoofed ? "YES" : "NO", cli->seed);

				if(check_seedscore(cli))
					if(ban_client(serv, cli, BAN_SEED))
						report_to_channel(serv,
								  "SEEDRAND -> Connecting client %s scored: %i",
								  cli->nick, cli->seed);

				check_dnsbl(serv, cli);
				return;
			}

			else if(!strcmp(array[0], "CLIEXIT"))
			{
				cli = hash_find_client(array[1]);
				if(cli == NULL)
				{
					logger(DEBUG,
					       "WARNING: (cliexit) user not found in client hash! nick: %s",
					       array[1]);
					return;
				}

				logger(DEBUG,
				       "DELETING USER: id[%i]: nick(%s) user(%s) host(%s) ip(%s) class(%s) spoofed(%s)",
				       cli->id, cli->nick, cli->user, cli->host, cli->ip,
				       cli->class, cli->isSpoofed ? "YES" : "NO");

				hash_del_client(cli);
				return;
			}

			// nick change
			// Nick change: From dubkat2 to dubkat3 [~dubkat2@2001:470::]
			else if(!strcmp(array[0], "Nick") && !strcmp(array[1], "change:"))
			{
				char old[MAX_NICKLEN];
				cli = hash_find_client(array[3]);

				if(cli == NULL)
				{
					logger(DEBUG,
					       "WARNING: (nick_change) user not found in client hash! nick: %s",
					       array[3]);
					return;
				}

				rb_snprintf(old, sizeof(old), "%s", cli->nick);
				rb_snprintf(cli->nick, strlen(array[5]) + 1, "%s", array[5]);

				cli->seed = seedscore(array[5]);

				logger(DEBUG, "NICK CHANGE: id[%d]: %s -> %s (seed: %i)",
				       cli->id, old, cli->nick, cli->seed);

				if(check_seedscore(cli))
					if(ban_client(serv, cli, BAN_SEED))
						report_to_channel(serv,
								  "SEEDRAND -> Nick change from %s to %s scored: %i",
								  old, cli->nick, cli->seed);

				return;
			}

			//NOTICE: Possible Flooder dubkat_[~suckit@2001:470:8b15::2] on irc.he.net target: #dronemon-devel
		}

		// XXX if we got to here, we just allocated memory for nothing
		rb_free(cli);

		if(strcasecmp(parv[1], "added") && strcasecmp(parv[2], "temporary"))
			return;

		logger(DEBUG, "Unhandled NOTICE: %s", parv[3]);
	}
}

// handle incoming privmsgs, either on chan, or msg. Currently only used for CTCP VERSION
static void
handle_PRIVMSG(struct conn *serv, int parc, char **parv)
{
	char source[12];	// XXX fix length
	char *token;
	struct my_clients *cli;
	struct status *me = &my_status;

	if(!strcmp(parv[2], me->currentnick))
	{
		// it's a private message
		nick_from_nuh(parv[0], source);
		if(!strcmp(parv[3], "\001VERSION\001"))
			sendto_one(serv, "NOTICE %s :\001VERSION %s-%s (%s)", source,
				   PACKAGE_NAME, PACKAGE_VERSION, PACKAGE_URL);
		return;
	}
	// parv[0](dubkat!dubkat@avada.kedavra) parv[1](PRIVMSG) parv[2](#dronemon-devel) parv[3](testing 123)
	// XXX
	// FIXME DANGER WILL ROBINSON!
	// XXX
	if(!strcmp(parv[2], "#ratbox-monitor"))
	{
		token = strtok(parv[3], " ");

		if(!strcmp(token, ".checklist"))
		{
			token = strtok(NULL, " ");
			char *blah[3] = { parv[0], "WALLOPS", token };
			handle_wallop(serv, 3, blah);
			return;
		}

		if(!strcmp(token, ".rbl"))
		{
			cli = rb_malloc(sizeof(struct my_clients));
			if(cli == NULL)
			{
				logger(CRIT, "ERROR: OUT OF MEMORY IN handle_PRIVMSG -> .rbl");
				exit(EXIT_FAILURE);
			}

			token = strtok(NULL, " ");
			// XXX what about lookup via hostname?
			rb_snprintf(cli->host, sizeof(cli->host), "%s", parv[2]);
			rb_snprintf(cli->ip, sizeof(cli->ip), "%s", token);
			cli->isSpoofed = NO;
			cli->isBanned = NO;
			logger(DEBUG, "WORKER: Being asked to check %s on %s", cli->ip, cli->host);
			check_dnsbl(serv, cli);
			return;
		}

		if (!strcmp(token, ".find" ) )
		{
			unsigned int found = 0;
			token = strtok(NULL, " ");

			elapse(START);
			found = hash_find_client_rx(serv, parv[2], token, 0);

			if ( !found ) {
				sendto_one(serv, "PRIVMSG %s :FIND -> no clients found. [%s]", parv[2], elapse(STOP) );
				return;
			}

			sendto_one(serv, "PRIVMSG %s :FIND -> found %d clients. [%s]", parv[2], found, elapse(STOP) );
			return;
		}
	}
	handle_UNKNOWN(serv, parc, parv);
}

static void
handle_PING(struct conn *serv, int parc, char **parv)
{
	sendto_one(serv, "PONG :%s", parv[1]);
}

/* some handy tools ripped right from ircd-ratbox that isn't in libratbox
 * ripped from match.c
 */

const unsigned char ToLowerTab[] = { 0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8,
	0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
	0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, ' ', '!',
	'"', '#', '$', '%', '&', 0x27, '(', ')', '*', '+', ',', '-', '.', '/',
	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=',
	'>', '?', '@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
	'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
	'z', '{', '|', '}', '~', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
	'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
	'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', 0x7f, 0x80, 0x81, 0x82,
	0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e,
	0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a,
	0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
	0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2,
	0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe,
	0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca,
	0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
	0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2,
	0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee,
	0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa,
	0xfb, 0xfc, 0xfd, 0xfe, 0xff
};

const unsigned char ToUpperTab[] = { 0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8,
	0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
	0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, ' ', '!',
	'"', '#', '$', '%', '&', 0x27, '(', ')', '*', '+', ',', '-', '.', '/',
	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=',
	'>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
	'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
	'Z', '[', '\\', ']', '^', 0x5f, '`', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
	'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
	'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', 0x7f, 0x80, 0x81, 0x82,
	0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e,
	0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a,
	0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
	0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2,
	0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe,
	0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca,
	0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
	0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2,
	0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee,
	0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa,
	0xfb, 0xfc, 0xfd, 0xfe, 0xff
};

/*
 * irccmp - case insensitive comparison of two 0 terminated strings.
 *
 *      returns  0, if s1 equal to s2
 *              <0, if s1 lexicographically less than s2
 *              >0, if s1 lexicographically greater than s2
 */
int
irccmp(const char *s1, const char *s2)
{
	const unsigned char *str1 = (const unsigned char *)s1;
	const unsigned char *str2 = (const unsigned char *)s2;
	int res;

	//s_assert(s1 != NULL);
	//s_assert(s2 != NULL);

	while((res = ToUpper(*str1) - ToUpper(*str2)) == 0)
	{
		if(*str1 == '\0')
			return 0;
		str1++;
		str2++;
	}
	return (res);
}

int
ircncmp(const char *s1, const char *s2, int n)
{
	const unsigned char *str1 = (const unsigned char *)s1;
	const unsigned char *str2 = (const unsigned char *)s2;
	int res;
	//s_assert(s1 != NULL);
	//s_assert(s2 != NULL);

	while((res = ToUpper(*str1) - ToUpper(*str2)) == 0)
	{
		str1++;
		str2++;
		n--;
		if(n == 0 || (*str1 == '\0' && *str2 == '\0'))
			return 0;
	}
	return (res);
}
